import threading
from typing import ContextManager, Optional


class ReadPriorityRWLock:
    def __init__(self):
        self._lock = threading.RLock()
        self._condition = threading.Condition(self._lock)

        self._reader_reentry: dict[int, int] = {}  # thread_id:reentry_times
        self._total_readers = 0

        self._writer_active = False
        self._writer_tid: Optional[int] = None
        self._writer_reentry = 0

    def gen_rlock(self) -> ContextManager[None]:
        outer = self

        class ReadLockContext:
            def __enter__(self) -> None:
                outer.acquire_read()

            def __exit__(self, exc_type, exc_val, exc_tb) -> None:
                outer.release_read()

        return ReadLockContext()

    def gen_wlock(self) -> ContextManager[None]:
        outer = self

        class WriteLockContext:
            def __enter__(self) -> None:
                outer.acquire_write()

            def __exit__(self, exc_type, exc_val, exc_tb) -> None:
                outer.release_write()

        return WriteLockContext()

    def acquire_read(self) -> None:
        current_tid = threading.get_ident()
        with self._lock:
            if self._writer_active and self._writer_tid == current_tid:
                self._total_readers += 1
                self._reader_reentry[current_tid] = self._reader_reentry.get(current_tid, 0) + 1
                return

            if current_tid in self._reader_reentry:
                self._reader_reentry[current_tid] += 1
                return

            while self._writer_active:
                self._condition.wait()
            self._total_readers += 1
            self._reader_reentry[current_tid] = 1

    def release_read(self) -> None:
        current_tid = threading.get_ident()
        with self._lock:
            if current_tid not in self._reader_reentry:
                raise RuntimeError("Thread does not hold read lock")

            self._reader_reentry[current_tid] -= 1
            if self._reader_reentry[current_tid] > 0:
                return

            del self._reader_reentry[current_tid]
            self._total_readers -= 1

            if self._total_readers == 0:
                self._condition.notify_all()

    def acquire_write(self) -> None:
        current_tid = threading.get_ident()
        with self._lock:
            if self._writer_active and self._writer_tid == current_tid:
                self._writer_reentry += 1
                return

            if current_tid in self._reader_reentry:
                raise RuntimeError("Cannot acquire write lock while holding read lock")

            while self._total_readers > 0 or self._writer_active:
                self._condition.wait()
            self._writer_active = True
            self._writer_tid = current_tid
            self._writer_reentry = 1

    def release_write(self) -> None:
        current_tid = threading.get_ident()
        with self._lock:
            if not self._writer_active or self._writer_tid != current_tid:
                raise RuntimeError("Thread does not hold write lock")

            self._writer_reentry -= 1
            if self._writer_reentry > 0:
                return

            self._writer_active = False
            self._writer_tid = None
            self._condition.notify_all()
